package com.think.cloud.thinkshop.mall.service.payment.impl;

import cn.hutool.core.util.NumberUtil;
import cn.hutool.json.JSONUtil;
import com.paypal.api.payments.*;
import com.paypal.base.rest.APIContext;
import com.paypal.base.rest.PayPalRESTException;
import com.stripe.model.PaymentIntent;
import com.stripe.model.Refund;
import com.stripe.param.RefundCreateParams;
import com.think.cloud.thinkshop.common.enums.payment.PaymentMethodEnum;
import com.think.cloud.thinkshop.mall.controller.admin.message.dto.MessageTemplateReplaceDTO;
import com.think.cloud.thinkshop.mall.controller.admin.order.vo.OrderDetailRespVO;
import com.think.cloud.thinkshop.mall.controller.app.order.vo.AppOrderInfoRespVO;
import com.think.cloud.thinkshop.mall.controller.app.payment.dto.RefundDTO;
import com.think.cloud.thinkshop.mall.controller.app.payment.dto.ToPayDTO;
import com.think.cloud.thinkshop.mall.controller.app.payment.vo.PayResultVO;
import com.think.cloud.thinkshop.mall.domain.memberuser.MemberUser;
import com.think.cloud.thinkshop.mall.domain.order.Order;
import com.think.cloud.thinkshop.mall.domain.payment.PaymentRecord;
import com.think.cloud.thinkshop.mall.domain.websitesetting.WebsiteSetting;
import com.think.cloud.thinkshop.mall.enums.message.MessageTemplateEnum;
import com.think.cloud.thinkshop.mall.service.memberuser.IMemberUserService;
import com.think.cloud.thinkshop.mall.service.message.IMessageTemplateService;
import com.think.cloud.thinkshop.mall.service.order.AppOrderService;
import com.think.cloud.thinkshop.mall.service.order.IOrderDetailService;
import com.think.cloud.thinkshop.mall.service.payment.IPaymentRecordService;
import com.think.cloud.thinkshop.mall.service.payment.PaymentService;
import com.think.cloud.thinkshop.mall.service.payment.paypal.config.PaypalPaymentIntent;
import com.think.cloud.thinkshop.mall.service.payment.paypal.config.PaypalPaymentMethod;
import com.think.cloud.thinkshop.mall.service.payment.paypal.config.PaypalProperties;
import com.think.cloud.thinkshop.mall.service.websitesetting.IWebsiteSettingService;
import com.think.common.core.exception.util.ServiceExceptionUtil;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.util.*;
import java.util.stream.Collectors;

import static com.think.cloud.thinkshop.common.enums.payment.PaymentStatusEnum.PAYMENT_STATUS_PAID_PART;
import static com.think.common.core.exception.enums.ErrorCode.PAYMENT_ERROR;

/**
 * @author zkthink
 * @apiNote
 **/
@Service
@Slf4j
public class PaymentServiceImpl implements PaymentService {
    @Autowired
    private APIContext apiContext;
    @Autowired
    private PaypalProperties paymentProperties;
    @Autowired
    private IPaymentRecordService paymentRecordService;
    @Autowired
    private AppOrderService appOrderService;
    @Autowired
    private IMemberUserService memberUserService;
    @Autowired
    private IWebsiteSettingService websiteSettingService;
    @Autowired
    private IMessageTemplateService messageTemplateService;
    @Autowired
    private IOrderDetailService iOrderDetailService;

    @Override
    public PayResultVO paypalPay(ToPayDTO dto, Long userId, String client) {
        Long orderId = dto.getOrderId();
        AppOrderInfoRespVO order = getOrder(orderId, userId);

        BigDecimal payAmount = order.getPayPrice();
        //货币
        String currencyCode = websiteSettingService.getCache().getCurrencyCode();
        try {
            Payment payment = createPayment(
                    payAmount,
                    currencyCode,
                    PaypalPaymentMethod.paypal,
                    PaypalPaymentIntent.sale,
                    "购买商品信息",
                    paymentProperties.getPaypalCancelUrl() + client,
                    paymentProperties.getPaypalSuccessUrl() + client) ;
            log.info("paypal预支付信息:{}", payment.toString());

            String payUrl = "";
            for (Links links : payment.getLinks()) {
                if (links.getRel().equals("approval_url")) {
                    payUrl = links.getHref();
                }
            }
            //保存paypal预支付信息
            savePaymentRecord(dto, payUrl, payment.getId(), JSONUtil.toJsonStr(payment), order, currencyCode);
            return new PayResultVO(payUrl);
        } catch (Exception e) {
            //保存paypal预支付异常信息 .......
            log.error(e.getMessage());
            throw ServiceExceptionUtil.exception(PAYMENT_ERROR);
        }
    }

    private void savePaymentRecord(ToPayDTO dto, String payUrl, String paymentId, String responseInfo, AppOrderInfoRespVO order, String currencyCode) {
        PaymentRecord paymentRecord = new PaymentRecord();
        paymentRecord.setCode(dto.getPayMethod());
        paymentRecord.setOrderId(dto.getOrderId());
        paymentRecord.setOrderCode(order.getOrderCode());
        paymentRecord.setPayUrl(payUrl);
        paymentRecord.setPaymentId(paymentId);
        paymentRecord.setResponseInfo(responseInfo);
        paymentRecord.setCurrencyCode(currencyCode);
        paymentRecordService.insertPaymentRecord(paymentRecord);
        log.info("订单:{},支付信息保存成功", dto.getOrderId());

        //订单状态修改为支付中
        appOrderService.updatePayStatus(dto.getOrderId(), PAYMENT_STATUS_PAID_PART.getCode());
    }

    @Override
    public Payment executePay(String paymentId, String payerId) {
        Payment payment = executePayment(paymentId, payerId);
        log.info("paypal支付执行:{}", payment.toString());
        //修改订单状态 根据paymentId查询订单编码
        PaymentRecord paymentRecord = paymentRecordService.selectByPaymentId(paymentId);
        String orderCode = paymentRecord.getOrderCode();
        if (payment.getState().equals("approved")) {
            List<Transaction> transactions = payment.getTransactions();
            Sale sale = transactions.get(0).getRelatedResources().get(0).getSale();
            String saleId = sale.getId();
            this.paySuccess(orderCode, saleId, paymentId, PaymentMethodEnum.PAYPAL.getCode());
        } else {
            this.payFailed(orderCode);
        }
        return payment;
    }

    private AppOrderInfoRespVO getOrder(Long orderId, Long userId) {
        AppOrderInfoRespVO orderDetailRespVO = appOrderService.get(orderId, userId);
//        if(orderDetailRespVO.getPaid().equals(PAYMENT_STATUS_PAID_PART.getCode())){
//            throw ServiceExceptionUtil.exception(ORDER_PAYMENT_PROCESS);
//        }
        return orderDetailRespVO;
    }


    @Override
    public void paySuccess(String orderCode, String latestCharge, String paymentIntentId, String payType) {
        appOrderService.paySuccess(orderCode, latestCharge, paymentIntentId, payType);
        log.info("支付成功,更新订单支付状态成功:{}", orderCode);
        Order order = appOrderService.selectOrderByOrderCode(orderCode);
        WebsiteSetting cache = websiteSettingService.getCache();

        List<OrderDetailRespVO> orderDetails = getOrderDetails(Arrays.asList(order.getId()));
        String productInfo = orderDetails.stream()
                .map(detail -> detail.getProductName() + " " + detail.getSku())
                .collect(Collectors.joining("，"));
        MessageTemplateReplaceDTO build = MessageTemplateReplaceDTO.builder()
                .product(productInfo)
                .orderCode(orderCode)
                .payPrice(order.getPayPrice().toString())
                .currencyUnit(cache.getCurrencySymbol())
                .orderId(order.getId())
                .build();
        messageTemplateService.sendMessageText(MessageTemplateEnum.TEMPLATE_3, build, order.getUserId());

    }
    private List<OrderDetailRespVO> getOrderDetails(List<Long> orderIds){
        return iOrderDetailService.selectOrderDetailListByOrderIds(orderIds);
    }

    @Override
    public void payFailed(String orderCode) {
        log.info("支付失败:{}", orderCode);
        appOrderService.payFailed(orderCode);
    }

    @Override
    public Boolean paymentStatus(Long orderId) {
        return appOrderService.getPaymentStatus(orderId);
    }

    @Override
    public Boolean createRefund(RefundDTO dto, String type) {
        return PaymentMethodEnum.PAYPAL.getCode().equals(type) ?
                createPaypalRefund(dto) : stripeCreateRefund(dto);
    }


    /**
     * @param total
     * @param currency
     * @param method
     * @param intent
     * @param description
     * @param cancelUrl
     * @param successUrl
     * @return
     * @throws PayPalRESTException
     */
    @SneakyThrows
    public Payment createPayment(BigDecimal total, String currency, PaypalPaymentMethod method, PaypalPaymentIntent intent,
                                 String description, String cancelUrl, String successUrl) {
        Amount amount = new Amount();
        amount.setCurrency(currency);
        amount.setTotal(String.format("%.2f", total));

        Transaction transaction = new Transaction();
        transaction.setDescription(description);
        transaction.setAmount(amount);

        List<Transaction> transactions = new ArrayList<>();
        transactions.add(transaction);

        Payer payer = new Payer();
        payer.setPaymentMethod(method.toString());

        Payment payment = new Payment();
        payment.setIntent(intent.toString());
        payment.setPayer(payer);
        payment.setTransactions(transactions);
        RedirectUrls redirectUrls = new RedirectUrls();
        redirectUrls.setCancelUrl(cancelUrl);
        redirectUrls.setReturnUrl(successUrl);
        payment.setRedirectUrls(redirectUrls);

        return payment.create(apiContext);
    }

    /**
     * @param paymentId
     * @param payerId
     * @return
     * @throws PayPalRESTException
     */
    @SneakyThrows
    public Payment executePayment(String paymentId, String payerId) {
        Payment payment = new Payment();
        payment.setId(paymentId);
        PaymentExecution paymentExecute = new PaymentExecution();
        paymentExecute.setPayerId(payerId);
        return payment.execute(apiContext, paymentExecute);
    }

    /**
     * 发起退款
     *
     * @param dto 退款信息
     * @return refundId 用户查询退款详情
     */
    @SneakyThrows
    public Boolean createPaypalRefund(RefundDTO dto) {
        String orderCode = dto.getOrderCode();
        BigDecimal total = dto.getTotal();
        String currency = dto.getCurrency();
        String paymentId = dto.getPaymentIntent();
        String saleId = dto.getLatestCharge();

        // 获取Sale对象
        Sale sale = Sale.get(apiContext, saleId);
        log.info("createRefund: req:{},sale:{}", dto, sale.toString());
        RefundRequest refundRequest = new RefundRequest();
        refundRequest.setInvoiceNumber(orderCode);
        Amount amount = new Amount();
        amount.setCurrency(currency);
        amount.setTotal(String.format("%.2f", total));
        refundRequest.setAmount(amount);

        DetailedRefund detailedRefund = sale.refund(apiContext, refundRequest);
        log.info("createRefund: req:{},resp:{}", dto, detailedRefund.toString());
        return "COMPLETED".equals(detailedRefund.getState());
    }

    //===================stripePay===========================
    @Override
    public PayResultVO stripePay(ToPayDTO dto, Long userId) {
        Long orderId = dto.getOrderId();
        AppOrderInfoRespVO order = getOrder(orderId, userId);
        String orderCode = order.getOrderCode();
        //用户信息
        MemberUser user = memberUserService.selectMemberUserById(userId);
        //货币
        String currencyCode = websiteSettingService.getCache().getCurrencyCode();
        try {
            Map<String, Object> params2 = new HashMap<>();
            params2.put("amount", NumberUtil.mul(order.getPayPrice(), 100).setScale(0, RoundingMode.HALF_UP));
            params2.put("currency", currencyCode.toLowerCase());
            params2.put("description", "think-shop");
            params2.put("receipt_email", user.getEmail());

            Map<String, Object> automaticPaymentMethods = new HashMap<>();
            automaticPaymentMethods.put("enabled", true);
            params2.put("automatic_payment_methods", automaticPaymentMethods);

            Map<String, Object> metadata = new HashMap<>();
            metadata.put("orderId", orderCode);
            params2.put("metadata", metadata);

            PaymentIntent paymentIntent2 = PaymentIntent.create(params2);
            log.debug("stripePay:{}", paymentIntent2.toString());

            String clientSecret = paymentIntent2.getClientSecret();

            //保存支付信息
            savePaymentRecord(dto, clientSecret, null, JSONUtil.toJsonStr(paymentIntent2), order, currencyCode);

            return new PayResultVO(clientSecret);
        } catch (Exception e) {
            log.error(e.getMessage());
            throw ServiceExceptionUtil.exception(PAYMENT_ERROR);
        }
    }

    /**
     * 发起退款
     *
     * @return refundId 用户查询退款详情
     */
    @SneakyThrows
    public Boolean stripeCreateRefund(RefundDTO dto) {
        String paymentIntent = dto.getPaymentIntent();
        BigDecimal total = dto.getTotal();
        String currency = dto.getCurrency();
        String charge = dto.getLatestCharge();

        RefundCreateParams refundCreateParams = RefundCreateParams
                .builder()
                .setAmount(NumberUtil.mul(total, 100).setScale(0, RoundingMode.HALF_UP).longValue())
                // .setCurrency(currency.toLowerCase())
                .setPaymentIntent(paymentIntent)
                .setReason(RefundCreateParams.Reason.DUPLICATE)
                // .setCharge(charge)
                .build();

        // 退款状态 succeeded, failed, or canceled
        Refund refund = Refund.create(refundCreateParams);
        log.info("stripeCreateRefund: req:{},resp:{}", dto, refund.toString());
        return "succeeded".equals(refund.getStatus());
    }

}
